#include "q3ds_process.h"

#include <QOpenGLContext>
#include <QOpenGLFunctions>

#include <QtMath>
#include <QDebug>
#include <QVector2D>
#include <QVector4D>
#include <QPixmap>

#include <lib3ds/camera.h>
#include <lib3ds/node.h>
#include <lib3ds/material.h>
#include <lib3ds/matrix.h>
#include <lib3ds/light.h>
#include <lib3ds/file.h>
#include <lib3ds/vector.h>

#include <qrender/q3ds_model.h>

Q3dsProcess::Q3dsProcess(const QString &path, double _a, double _alpha,
                         double _d, double _theta)
    : file_path_(path), anti_alias_(1), file_(0), a(_a), alpha(_alpha), d(_d),
      theta(_theta)

{
}

void Q3dsProcess::loadModel()
{
    // 加载 3ds 文件
    file_ = lib3ds_file_load(file_path_.toStdString().c_str());
    if (!file_) {
        qDebug("Q3dsProcess::loadModel: Error: Loading 3DS file[%s] failed.\n",
               file_path_.toStdString().c_str());
        return;
    }

    // 如果没有 nodes，则创建 nodes 来显示所有的 meshes
    // 这种处理方式可能是为了将原本以不同的方式表示的网格数据转换成节点数据，
    // 从而更方便地处理和操作。例如，可以在 file_->nodes
    // 链表中使用节点的相关函数和操作，而不需要额外处理网格数据。
    if (!file_->nodes) {
        for (auto mesh = file_->meshes; !mesh; mesh = mesh->next) {
            Lib3dsNode *node = lib3ds_node_new_object();

            strcpy(node->name, mesh->name);
            node->parent_id = LIB3DS_NO_PARENT;
            lib3ds_file_insert_node(file_, node);
        }
    }

    // 对所有节点进行求值（评估）
    // 时间值 t 的取值范围在 0 到 file_->frames 之间，用于表示动画的当前时间
    lib3ds_file_eval(file_, 1.0f);

    // 获取节点的边界框
    // 边界框是一个立方体，用于包围指定节点集合的所有顶点，以便于在3D空间中进行位置和尺寸的计算
    // 只计算 meshes 节点的边界框，不计算相机节点和光照节点。
    Lib3dsVector bmin_, bmax_;
    lib3ds_file_bounding_box_of_nodes(file_, LIB3DS_TRUE, LIB3DS_FALSE,
                                      LIB3DS_FALSE, bmin_, bmax_);

    float sx_, sy_, sz_, size_; /* bounding box dimensions */
    float cx_, cy_, cz_;        /* bounding box center */
    sx_ = bmax_[0] - bmin_[0];
    sy_ = bmax_[1] - bmin_[1];
    sz_ = bmax_[2] - bmin_[2];
    size_ = qMax(sx_, sy_);
    size_ = qMax(size_, sz_);
    cx_ = (bmin_[0] + bmax_[0]) / 2;
    cy_ = (bmin_[1] + bmax_[1]) / 2;
    cz_ = (bmin_[2] + bmax_[2]) / 2;

    // 如果没有相机，添加 4 个视角的相机 x，y，z，ISO
    if (!file_->cameras) {
        /* Add some cameras that encompass the bounding box */
        Lib3dsCamera *camera = lib3ds_camera_new("camera_X");
        camera->target[0] = cx_;
        camera->target[1] = cy_;
        camera->target[2] = cz_;
        memcpy(camera->position, camera->target, sizeof(camera->position));
        camera->position[0] = bmax_[0] + 1.5 * qMax(sy_, sz_);
        camera->near_range = (camera->position[0] - bmax_[0]) * .5;
        camera->far_range = (camera->position[0] - bmin_[0]) * 2;
        lib3ds_file_insert_camera(file_, camera);

        /* Since lib3ds considers +Y to be into the screen, we'll put
         * this camera on the -Y axis, looking in the +Y direction.
         */
        camera = lib3ds_camera_new("camera_Y");
        camera->target[0] = cx_;
        camera->target[1] = cy_;
        camera->target[2] = cz_;
        memcpy(camera->position, camera->target, sizeof(camera->position));
        camera->position[1] = bmin_[1] - 1.5 * qMax(sx_, sz_);
        camera->near_range = (bmin_[1] - camera->position[1]) * .5;
        camera->far_range = (bmax_[1] - camera->position[1]) * 2;
        lib3ds_file_insert_camera(file_, camera);

        camera = lib3ds_camera_new("camera_Z");
        camera->target[0] = cx_;
        camera->target[1] = cy_;
        camera->target[2] = cz_;
        memcpy(camera->position, camera->target, sizeof(camera->position));
        camera->position[2] = bmax_[2] + 1.5 * qMax(sx_, sy_);
        camera->near_range = (camera->position[2] - bmax_[2]) * .5;
        camera->far_range = (camera->position[2] - bmin_[2]) * 2;
        lib3ds_file_insert_camera(file_, camera);

        camera = lib3ds_camera_new("camera_ISO");
        camera->target[0] = cx_;
        camera->target[1] = cy_;
        camera->target[2] = cz_;
        memcpy(camera->position, camera->target, sizeof(camera->position));
        camera->position[0] = bmax_[0] + .75 * size_;
        camera->position[1] = bmin_[1] - .75 * size_;
        camera->position[2] = bmax_[2] + .75 * size_;
        camera->near_range = (camera->position[0] - bmax_[0]) * .5;
        camera->far_range = (camera->position[0] - bmin_[0]) * 3;
        lib3ds_file_insert_camera(file_, camera);
    }

    // NOTE: 不理解为什么还判断一次，前面刚插入完
    // 这里怎么可能还为空，是有特殊情况吗？
    if (!file_->cameras) {
        fputs("3dsplayer: Error: No camera found.\n", stderr);
        lib3ds_file_free(file_);
        file_ = 0;
        exit(1);
    }

    lib3ds_file_eval(file_, 0.);
}

Q3dsMesh *Q3dsProcess::getMesh(float angle, QMatrix4x4 &translation_matrix)
{
    setting();

    // 构建变换矩阵
    mesh_->dh_matrix = translation_matrix;
    // DH 参数变换
    mesh_->dh_matrix.rotate(alpha, 1.0f, 0.0f, 0.0f);
    mesh_->dh_matrix.translate(a, 0.0f, 0.0f);
    mesh_->dh_matrix.rotate(theta, 0.0f, 0.0f, 1.0f);
    mesh_->dh_matrix.translate(0.0f, 0.0f, d);
    // 旋转关节
    mesh_->dh_matrix.rotate(angle, 0.0f, 0.0f, 1.0f);

    translation_matrix = mesh_->dh_matrix;

    return mesh_;
}

void Q3dsProcess::setting()
{
    if (!file_) {
        return;
    }

    // 渲染所有节点
    // NOTE: nodes 永远只有一个
    for (auto p = file_->nodes; p != 0; p = p->next) {
        renderNode(p);
    }
}

void Q3dsProcess::renderNode(Lib3dsNode *node)
{
    Q_ASSERT(file_);

    // NOTE: childs 好像都是空的
    for (auto p = node->childs; 0 != p; p = p->next) {
        renderNode(p);
    }

    if (node->type != LIB3DS_OBJECT_NODE) {
        return;
    }

    // 避免渲染无用的虚拟节点
    if (!strcmp(node->name, "$$$DUMMY")) {
        return;
    }

    // 先从对象中获取边界框
    auto mesh = lib3ds_file_mesh_by_name(file_, node->data.object.morph);
    if (!mesh) { // 如果没有，再从节点中获取
        mesh = lib3ds_file_mesh_by_name(file_, node->name);
        if (!mesh) { // 如果还没有，则结束无效的渲染
            return;
        }
    }

    // 是否已经绘制
    if (mesh->user.p) {
        mesh_ = static_cast<Q3dsMesh *>(mesh->user.p);
        return;
    }

    Q3dsMesh *q3ds_mesh = new Q3dsMesh;
    mesh->user.p = q3ds_mesh;

    q3ds_mesh->background_solid_use = file_->background.solid.use;
    if (q3ds_mesh->background_solid_use) {
        q3ds_mesh->background_solid_color = QColor::fromRgbF(
            file_->background.solid.col[0], file_->background.solid.col[1],
            file_->background.solid.col[2]);
    }

    /* TODO: fog */

    Lib3dsMaterial *last_mat = nullptr;
    for (auto i = 0; i < mesh->faces; ++i) {
        Lib3dsFace *face_l = &mesh->faceL[i]; // 面
        Q3dsFace q3ds_face;

        if (face_l->material[0]) {
            // 根据名称查找 3ds 文件中的材质
            auto mat = lib3ds_file_material_by_name(file_, face_l->material);

            // 获取正面材质属性
            q3ds_face.diffuse = QVector4D(mat->diffuse[0], mat->diffuse[1],
                                          mat->diffuse[2], mat->diffuse[3]);
        }

        // 获取法向量、顶点数据
        q3ds_face.normal =
            QVector3D(face_l->normal[0], face_l->normal[1], face_l->normal[2]);
        q3ds_face.points << QVector3D(mesh->pointL[face_l->points[0]].pos[0],
                                      mesh->pointL[face_l->points[0]].pos[1],
                                      mesh->pointL[face_l->points[0]].pos[2])
                         << QVector3D(mesh->pointL[face_l->points[1]].pos[0],
                                      mesh->pointL[face_l->points[1]].pos[1],
                                      mesh->pointL[face_l->points[1]].pos[2])
                         << QVector3D(mesh->pointL[face_l->points[2]].pos[0],
                                      mesh->pointL[face_l->points[2]].pos[1],
                                      mesh->pointL[face_l->points[2]].pos[2]);

        q3ds_mesh->faces << q3ds_face;
    }

    mesh_ = q3ds_mesh;
}
